{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "import matplotlib\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import tensorflow as tf\n",
    "import scipy.io\n",
    "\n",
    "import os, re\n",
    "\n",
    "import claude.utils as cu\n",
    "import claude.claudeflow.autoencoder as ae\n",
    "import claude.claudeflow.helper as cfh\n",
    "import claude.claudeflow.training as cft\n",
    "import claude.claudeflow.models.NLIN as cfnlin"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "seed = 42\n",
    "tf.set_random_seed(seed)\n",
    "np.random.seed(seed)\n",
    "\n",
    "# Parameters\n",
    "# Channel Parameters\n",
    "chParam = cu.AttrDict()\n",
    "chParam.M\t\t  = 64\n",
    "chParam.D\t\t  = 16.4640\n",
    "chParam.nPol\t  = 2\n",
    "chParam.PdBm\t  = 0\n",
    "chParam.nSpans\t  = 20\n",
    "chParam.channels = np.array([-100., -50., 0., 50., 100.])\n",
    "\n",
    "# Auto-Encoder Parameters\n",
    "aeParam = cu.AttrDict()\n",
    "aeParam.constellationDim\t= 2\n",
    "aeParam.constellationOrder\t= chParam.M\n",
    "aeParam.nLayers\t\t= 2\n",
    "aeParam.nHidden \t= 32\n",
    "aeParam.activation  = tf.nn.selu\n",
    "aeParam.dtype       = tf.float32\n",
    "aeParam.GN\t        = False\n",
    "aeParam.optimizeP   = True\n",
    "\n",
    "# Training Parameters\n",
    "trainingParam = cu.AttrDict()\n",
    "trainingParam.sampleSize\t= 128*chParam.M\n",
    "trainingParam.batchSize \t= 16*chParam.M  # Increase for better results (especially if M>16)\n",
    "trainingParam.learningRate\t= 0.001\n",
    "trainingParam.displayStep\t= 50\n",
    "trainingParam.path\t\t\t= 'results'\n",
    "trainingParam.filename\t\t= 'P{}_M{}_nSpans{}'.format(chParam.PdBm,chParam.M,chParam.nSpans)\n",
    "trainingParam.saveWeights\t= False\n",
    "trainingParam.earlyStopping = 250\n",
    "trainingParam.iterations    = 250\n",
    "trainingParam.summaries     = True\n",
    "trainingParam.endWithLargeBatch = True\n",
    "\n",
    "# create string identifier for tensorboard\n",
    "if trainingParam.summaries:\n",
    "    chHyperParam = ['M','PdBm','nSpans']\n",
    "    aeHyperParam = ['GN']\n",
    "    trainingHyperParam = ['sampleSize','batchSize','learningRate']\t\n",
    "\n",
    "    trainingParam.summaryString = ','.join(  [ '{}={}'.format(item,chParam[item]) for item in chHyperParam ]\n",
    "                                            +[ '{}={}'.format(item,trainingParam[item]) for item in trainingHyperParam ]\n",
    "                                            +[ '{}={}'.format(item,aeParam[item]) for item in aeHyperParam ] )\n",
    "\n",
    "# TF constants\n",
    "two = tf.constant(2,aeParam.dtype)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "from claude.models.NLIN import defaultParameters,\\\n",
    "                               calcConstants,\\\n",
    "                               calcIntraChannelNLIN,calcInterChannelNLIN,\\\n",
    "                               calcIntraChannelGN,calcInterChannelGN"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "# NLIN model\n",
    "p = defaultParameters( D=chParam.D )\n",
    "p.PdBm = chParam.PdBm\n",
    "p.nSpans = chParam.nSpans\n",
    "p.nPol = chParam.nPol\n",
    "p.channels = chParam.channels\n",
    "p.nChannels = len(chParam.channels)\n",
    "\n",
    "aseNoisePower, interConst, intraConst, interConstAdd, intraConstAdd = calcConstants(p)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "WARNING: Logging before flag parsing goes to stderr.\n",
      "W0921 22:35:51.178254 139656910841664 lazy_loader.py:50] \n",
      "The TensorFlow contrib module will not be included in TensorFlow 2.0.\n",
      "For more information, please see:\n",
      "  * https://github.com/tensorflow/community/blob/master/rfcs/20180907-contrib-sunset.md\n",
      "  * https://github.com/tensorflow/addons\n",
      "  * https://github.com/tensorflow/io (for I/O related ops)\n",
      "If you depend on functionality not listed there, please file an issue.\n",
      "\n",
      "W0921 22:35:51.198022 139656910841664 deprecation.py:323] From /home/rasmus/.conda/envs/claudeOnline/lib/python3.6/site-packages/tensorflow/python/ops/distributions/util.py:1196: add_dispatch_support.<locals>.wrapper (from tensorflow.python.ops.array_ops) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Use tf.where in 2.0, which has the same broadcast rule as np.where\n"
     ]
    }
   ],
   "source": [
    "# Model constants to tensorflow\n",
    "sigma2_noise = tf.constant( aseNoisePower, aeParam.dtype)\n",
    "intra_const = tf.expand_dims( tf.constant( intraConst, aeParam.dtype), axis=1)\n",
    "inter_const = tf.constant( interConst, aeParam.dtype)\n",
    "intra_const_add = tf.constant( intraConstAdd, aeParam.dtype )\n",
    "inter_const_add = tf.constant( interConstAdd, aeParam.dtype )\n",
    "\n",
    "# learn optimal power or not\n",
    "if aeParam.optimizeP:\n",
    "    P0 = cu.dB2lin( chParam.PdBm, 'dBm')\n",
    "    P0 = tf.constant(P0, aeParam.dtype)\n",
    "    P0 = tf.contrib.distributions.softplus_inverse( P0 )\n",
    "    P0 = tf.nn.softplus(tf.Variable( P0 ))    \n",
    "else:\n",
    "    P0 = tf.constant( cu.dB2lin( chParam.PdBm, 'dBm'), aeParam.dtype )\n",
    "    \n",
    "PdBm = cfh.lin2dB(P0, 'dBm')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "W0921 22:35:51.270394 139656910841664 deprecation.py:506] From /home/rasmus/.conda/envs/claudeOnline/lib/python3.6/site-packages/tensorflow/python/ops/init_ops.py:1251: calling VarianceScaling.__init__ (from tensorflow.python.ops.init_ops) with dtype is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Call initializer instance with the dtype argument instead of passing it to the constructor\n"
     ]
    }
   ],
   "source": [
    "# Tensorflow input\n",
    "X = tf.placeholder( aeParam.dtype, shape=(None, chParam.M) )\n",
    "\n",
    "# Auto-encoder\n",
    "enc, enc_seed = ae.encoder(X, aeParam)\n",
    "enc_power = tf.sqrt( P0 ) * enc\n",
    "enc_seed_power = tf.sqrt( P0 ) * enc_seed"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "# NLIN or GN model\n",
    "if aeParam.GN:\n",
    "    sigma2_inter = cfnlin.calcInterChannelGN(inter_const,P0,chParam.nPol,dtype=aeParam.dtype)\n",
    "    sigma2_intra = cfnlin.calcIntraChannelGN(intra_const,P0,chParam.nPol,dtype=aeParam.dtype)\n",
    "    \n",
    "    sigma2_nlin = tf.reduce_sum( sigma2_inter ) + tf.reduce_sum( sigma2_inter )\n",
    "else:\n",
    "    # kur = mean(abs(const).^4)/mean(abs(const).^2).^2; % Second order modulation factor <|a|^4>/<|a|^2>^2\n",
    "    # kur3 = mean(abs(const).^6)/mean(abs(const).^2).^3; % Third order modulation factor <|a|^6>/<|a|^2>^3\n",
    "    constellation_abs = cfh.norm(enc_seed_power)\n",
    "    kur  = tf.reduce_mean(tf.pow(constellation_abs,4))/tf.pow(P0,2)\n",
    "    kur3 = tf.reduce_mean(tf.pow(constellation_abs,6))/tf.pow(P0,3)\n",
    "    sigma2_inter = cfnlin.calcInterChannelNLIN(inter_const,kur,P0,chParam.nPol,dtype=aeParam.dtype)\n",
    "    sigma2_intra = cfnlin.calcIntraChannelNLIN(intra_const,kur,kur3,P0,chParam.nPol,dtype=aeParam.dtype)\n",
    "    \n",
    "    sigma2_intra_add = cfnlin.calcIntraChannelNLIN(intra_const_add,kur,kur3,P0,chParam.nPol,dtype=aeParam.dtype)\n",
    "    sigma2_inter_add = cfnlin.calcInterChannelNLINAddTerms(inter_const_add,kur,P0,chParam.nPol,dtype=aeParam.dtype)\n",
    "\n",
    "    sigma2_nlin = tf.reduce_sum( sigma2_intra ) + tf.reduce_sum( sigma2_inter ) + tf.reduce_sum( sigma2_intra_add ) + tf.reduce_sum( sigma2_inter_add ) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "sigma2 = sigma2_noise + sigma2_nlin\n",
    "noise = tf.sqrt( sigma2 )\\\n",
    "            *tf.rsqrt(two)\\\n",
    "            *tf.random_normal(shape=tf.shape(enc_power),dtype=aeParam.dtype)\n",
    "\n",
    "SNR = cfh.lin2dB( P0 / sigma2, 'dB' )\n",
    "\n",
    "channel = enc_power + noise\n",
    "channel_norm = channel * tf.rsqrt( P0 )\n",
    "\n",
    "decoder = ae.decoder(channel_norm,aeParam)\n",
    "softmax = tf.nn.softmax(decoder)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Gaussian MI\n",
    "enc_seed_cp = tf.expand_dims( tf.complex( enc_seed[:,0], enc_seed[:,1] ), axis=0 )\n",
    "enc_cp = tf.expand_dims( tf.complex( enc[:,0], enc[:,1] ), axis=0 )\n",
    "channel_cp = tf.expand_dims( tf.complex( channel_norm[:,0], channel_norm[:,1] ), axis=0 )\n",
    "gaussian_MI = cfh.gaussianMI(enc_cp, channel_cp, enc_seed_cp, chParam.M, dtype=aeParam.dtype)\n",
    "\n",
    "# Neural Network MI, with -> softmax = P(x|y)\n",
    "Px = tf.constant( 1/chParam.M, aeParam.dtype )\n",
    "softmax_MI = cfh.softmaxMI(softmax, X, Px)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Metrics, Loss etc\n",
    "correct_prediction = tf.equal(tf.argmax(X,1), tf.argmax(decoder,1))\n",
    "accuracy = tf.reduce_mean(tf.cast(correct_prediction, aeParam.dtype))\n",
    "\n",
    "loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(labels=X,logits=decoder))\n",
    "optimizer = tf.train.AdamOptimizer(learning_rate=trainingParam.learningRate).minimize(loss)\n",
    "\n",
    "metricsDict = {'xentropy_metric':loss, 'accuracy_metric':accuracy, 'softmax_MI_metric':softmax_MI, 'gaussian_MI_metric':gaussian_MI}\n",
    "meanMetricOpsDict, updateOps, resetOps = cft.create_mean_metrics(metricsDict)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "init = tf.global_variables_initializer()\n",
    "sess = tf.Session()\n",
    "sess.run(init)\n",
    "\n",
    "# summaries for tensorboard\n",
    "if trainingParam.summaries:\n",
    "    # Summaries\n",
    "    s = [tf.summary.scalar('accuracy', meanMetricOpsDict['accuracy_metric']),\n",
    "         tf.summary.scalar('xentropy', meanMetricOpsDict['xentropy_metric']),\n",
    "         tf.summary.scalar('softmax_MI_metric', meanMetricOpsDict['softmax_MI_metric']),\n",
    "         tf.summary.scalar('gaussian_MI_metric', meanMetricOpsDict['gaussian_MI_metric']),\n",
    "         tf.summary.scalar('Plin', P0),\n",
    "         tf.summary.scalar('PdBm', PdBm),\n",
    "         tf.summary.scalar('sigma2_noise', sigma2_noise),\n",
    "         tf.summary.scalar('sigma2_nlin', sigma2_nlin)]\n",
    "\n",
    "    if not aeParam.GN:\n",
    "        s.extend( [tf.summary.scalar('kur', kur),\n",
    "                   tf.summary.scalar('kur3', kur3)] )\n",
    "\n",
    "    summaries = tf.summary.merge_all()\n",
    "\n",
    "    summaries_dir = os.path.join(trainingParam.path,'tboard',trainingParam.summaryString)\n",
    "    os.makedirs(summaries_dir, exist_ok=True)\n",
    "\n",
    "    train_writer = tf.summary.FileWriter(summaries_dir + '/train', sess.graph)\n",
    "else:\n",
    "    train_writer = None\n",
    "\n",
    "# save process\n",
    "saver = tf.train.Saver()\n",
    "checkpoint_path = os.path.join(trainingParam.path,'checkpoint',trainingParam.filename,'best')\n",
    "if not os.path.exists(checkpoint_path):\n",
    "    os.makedirs(checkpoint_path)\n",
    "else:\n",
    "    pass\n",
    "    #print(\"Restoring checkpoint...\", flush=True)\n",
    "    #saver.restore(sess=sess,save_path=checkpoint_path)\n",
    "\n",
    "# early stopping and keeping the best result\n",
    "bestLoss = 100000\n",
    "bestAcc = 0\n",
    "lastImprovement = 0\n",
    "\n",
    "nBatches = int(trainingParam.sampleSize/trainingParam.batchSize)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "x, _, xSeed = cu.hotOnes(trainingParam.batchSize,(1,0),chParam.M)\n",
    "feedDict = { X: x }"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoche: 50 - avDecLoss: 1.31 - avAccuracy: 0.59 - avSoftmaxMI: 4.11 - avGaussianMI: 4.61 - outPdBm: 0.17\n",
      "epoche: 100 - avDecLoss: 1.00 - avAccuracy: 0.63 - avSoftmaxMI: 4.56 - avGaussianMI: 4.65 - outPdBm: 0.21\n",
      "epoche: 150 - avDecLoss: 0.94 - avAccuracy: 0.65 - avSoftmaxMI: 4.65 - avGaussianMI: 4.69 - outPdBm: 0.22\n",
      "epoche: 200 - avDecLoss: 0.94 - avAccuracy: 0.65 - avSoftmaxMI: 4.64 - avGaussianMI: 4.65 - outPdBm: 0.22\n",
      "epoche: 250 - avDecLoss: 0.94 - avAccuracy: 0.65 - avSoftmaxMI: 4.64 - avGaussianMI: 4.65 - outPdBm: 0.22\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "W0921 22:36:52.642401 139656910841664 deprecation.py:323] From /home/rasmus/.conda/envs/claudeOnline/lib/python3.6/site-packages/tensorflow/python/training/saver.py:1276: checkpoint_exists (from tensorflow.python.training.checkpoint_management) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Use standard file APIs to check for files with this prefix.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Final - Loss: 0.92 - Accuracy: 0.66 \n"
     ]
    }
   ],
   "source": [
    "# training\n",
    "for epoche in range(1, trainingParam.iterations+1):\n",
    "    # end with larger batchsize\n",
    "    if trainingParam.endWithLargeBatch and epoche > 0.75 * trainingParam.iterations:\n",
    "        batchSize = trainingParam.sampleSize\n",
    "    else:        \n",
    "        batchSize = trainingParam.batchSize\n",
    "        \n",
    "    sess.run(resetOps)\n",
    "    \n",
    "    for batch in range(0,nBatches):\n",
    "        x, _, xSeed = cu.hotOnes(batchSize,(1,0),chParam.M,seed=batch)\n",
    "        \n",
    "        feedDict = { X: x }\n",
    "        sess.run([optimizer, updateOps], feed_dict=feedDict)\n",
    "    \n",
    "    [outPdBm, outSigma2Nlin, outSigma2, latestConstellation] = sess.run([PdBm, sigma2_nlin, sigma2, enc_seed], feed_dict=feedDict)\n",
    "    [avLoss, avAccuracy, avSoftmaxMI, avGaussianMI] = sess.run(list(meanMetricOpsDict.values()))\n",
    "    \n",
    "    if trainingParam.summaries:\n",
    "        outSummaries = sess.run(summaries, feed_dict=feedDict)\n",
    "        train_writer.add_summary(outSummaries,epoche)\n",
    "\n",
    "    if avLoss < bestLoss:\n",
    "        bestLoss = avLoss\n",
    "        bestAcc = avAccuracy\n",
    "        lastImprovement = epoche\n",
    "        saver.save(sess=sess,save_path=checkpoint_path)\n",
    "\n",
    "    if epoche - lastImprovement > trainingParam.earlyStopping:\n",
    "        print(\"Breaking due to no improvement\")\n",
    "        break;        \n",
    "\n",
    "    if epoche%trainingParam.displayStep == 0:\n",
    "        print('epoche: {} - avDecLoss: {:.2f} - avAccuracy: {:.2f} - avSoftmaxMI: {:.2f} - avGaussianMI: {:.2f} - outPdBm: {:.2f}'.format(epoche,avLoss,avAccuracy,avSoftmaxMI,avGaussianMI,outPdBm),flush=True)\n",
    "        \n",
    "print('Final - Loss: {:.2f} - Accuracy: {:.2f} '.format(bestLoss,bestAcc))\n",
    "saver.restore(sess=sess,save_path=checkpoint_path)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "latestConstellation = sess.run(enc_seed, feed_dict=feedDict)\n",
    "plt.plot(latestConstellation[:,0],latestConstellation[:,1],'.')\n",
    "plt.axis('square');\n",
    "lim_ = 1.6\n",
    "plt.xlim(-lim_,lim_);\n",
    "plt.ylim(-lim_,lim_);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "if trainingParam.saveWeights:\n",
    "    if not os.path.exists(trainingParam.path):\n",
    "        os.makedirs(trainingParam.path)\n",
    "    myVars = {re.sub(r'\\/|\\:\\d','',x.name):x.eval(session=sess) for x in tf.trainable_variables()}\n",
    "    fName = 'NNvars_{}'.format(trainingParam.filename)\n",
    "    scipy.io.savemat(os.path.join(trainingParam.path,fName), myVars)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "import claude.tx as tx\n",
    "import claude.models.NLIN as nlin\n",
    "nlinPowerSweep = np.arange(-8, 4+1, 0.1)\n",
    "\n",
    "# System parameters\n",
    "qamParam = p\n",
    "\n",
    "# QAM\n",
    "qam_constellation = np.squeeze(tx.qammod(chParam.M))\n",
    "qamParam.kur, qamParam.kur3 = nlin.calcKur(qam_constellation)\n",
    "qamEffSNR = nlinPowerSweep - nlin.calcNLIN(qamParam, nlinPowerSweep, aseNoisePower, interConst, intraConst, interConstAdd, intraConstAdd)\n",
    "\n",
    "outSNR = sess.run(SNR)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.plot(nlinPowerSweep, qamEffSNR, 'b')\n",
    "plt.plot(outPdBm, outSNR, 'xr')\n",
    "plt.title('Power [dBm] VS effective SNR')\n",
    "plt.xlabel('Power [dBm]')\n",
    "plt.ylabel('effective SNR [dB]')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "qamMI = np.zeros(np.shape(qamEffSNR))\n",
    "N = 5e4\n",
    "\n",
    "for ii,p in enumerate(nlinPowerSweep):\n",
    "    qamMI[ii] = cu.SNRtoMI(N, qamEffSNR[ii], np.expand_dims(qam_constellation, 0))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.plot(nlinPowerSweep, qamMI, 'b')\n",
    "plt.plot(outPdBm, avGaussianMI, 'xr')\n",
    "plt.title('Power [dBm] VS MI')\n",
    "plt.xlabel('Power [dBm]')\n",
    "plt.ylabel('MI [bits]')\n",
    "plt.show()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
